#!/usr/bin/python2
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab:fileencoding=utf-8

'''
        @author: msune, cbermudo, omoya
    @organization: i2CAT,  OFELIA FP7

    PolicyEngine Condition class
    Encapsulates logic of a simple condition
'''

'''Supported operators'''
_collectionOperators= ["in"]
_logicalOperators = ["&&", "||"]
_rangeOperators = ["[]", "{}"]
_comparisonOperators = ["=", "!=", ">", "<", ">=", "<="]
_operators = _comparisonOperators + _logicalOperators + _rangeOperators + _collectionOperators


'''Collection inner class'''
class Collection(list):

    def __init__(self, coll):
        self +=coll

    @staticmethod
    def isCollection(x):
        return isinstance(x, Collection)

'''Range inner class'''
class Range(object):
    upperLimit=None
    lowerLimit=None

    @staticmethod
    def isRange(x):
        return isinstance(x, Range)

    @staticmethod
    def isInRange(x, urange):
        if not Range.isRange(urange):
            raise ValueError("Unknown data type; must be a Range instance")
        return urange.lowerLimit <= x <= urange.upperLimit

    @staticmethod
    def isInRangeStrict(x, urange):
        if not Range.isRange(urange):
            raise ValueError("Unknown data type; must be a Range instance")
        return urange.lowerLimit < x < urange.upperLimit

    def __init__(self, lower, upper):
        if int(upper)<int(lower):
            raise ValueError("Cannot create Range; upper limit must be equal or higher that lower limit")

        self.upperLimit = upper
        self.lowerLimit = lower


    def __str__(self):
    #   return "(%s, %s)"%(str(self.lowerLimit), str(self.upperLimit))
        return "%s, %s"%(str(self.lowerLimit), str(self.upperLimit))

'''Condition class'''
class Condition(object):

    #Class attributes
    _leftOperand = None
    _rightOperand = None

    _operator=None
    _negate=None

    #Getters
    def getNegate(self):
        return self._negate
    def getOperator(self):
        return self._operator
    def getLeftOperand(self):
        return self._leftOperand
    def getRightOperand(self):
        #return self._rightOperand
        return self._rightOperand

    #Constructor
    def __init__(self,  left,  right,  operator, negate=False):

        self._leftOperand = left
        self._rightOperand = right
        self._operator = operator
        self._negate = negate

        #Make sure user is not comparing Conditions and Literals/Values
        if Condition.isCondition(self._rightOperand) is not Condition.isCondition(self._leftOperand):
            raise Exception("Nested Condition can only be logically operated with another Condition")

        #Check if one of the operands is a Condition
        if ( Condition.isCondition(self._leftOperand) ) and operator not in _logicalOperators:
            raise Exception("Cannot evaluate expression. Only logical operators can be used while using nested Conditions")

        if operator in _logicalOperators and (not Condition.isCondition(self._leftOperand) ):
            raise Exception("Cannot evaluate expression. Logical operators can only be used with nested Conditions")

        #If operator is a range Operator,  check left or right is a Range instance
        if self._operator in _rangeOperators and ( not ( Range.isRange(self._rightOperand) or Range.isRange(self._leftOperand) )):
            raise Exception("Cannot operate with a range without using Range instance")

        #If operator is a collection,  check right is a Collection instance
        if self._operator in _collectionOperators and not Collection.isCollection(self._rightOperand):
            raise Exception("Cannot operate with a Collection without using Collection instance")

        #Check operator
        if self._operator not in _operators:
            raise Exception("Unknown operator")

        #Check operator
        if not isinstance(self._negate, bool):
            raise Exception("Unknown negate value")

    @staticmethod
    def isCondition(x):
        return isinstance(x,  Condition)

    def dump(self):
        if ( Range.isRange(self._rightOperand) ):
            negate=""
            if self._negate:
                negate="not "
            if ( self._operator == '[]' ):
                return " %s %s in range [%s] "%(negate, self._leftOperand, str(self._rightOperand))
            else:
                return " %s %s in range {%s} "%(negate, self._leftOperand, str(self._rightOperand))

        if ( Collection.isCollection(self._rightOperand) ):
            negate = ""
            if self._negate:
                negate="not "
            rightOp = "collection {"
            for item in self._rightOperand:
                rightOp = rightOp + str(item) + ", "
            rightOp = rightOp[:-1] + "}"
            return " %s %s in %s "%(negate, self._leftOperand, rightOp)

        if Condition.isCondition(self._leftOperand):
            negate = ""
            if self._negate:
                negate = "not "
            return " %s (%s) %s (%s)  "%(negate, self._leftOperand.dump(), str(self._operator), self._rightOperand.dump())
        else:
            negate=""
            if self._negate:
                negate="not "
            return " %s %s %s %s   "%(negate, self._leftOperand, str(self._operator), str(self._rightOperand))


    #Evaluate condition
    def evaluate(self,  metaObj, resolver):
        #Retrieve operand value
        rightValue=None
        leftValue=None

        if Condition.isCondition(self._leftOperand):
            leftValue = self._leftOperand.evaluate(metaObj, resolver)
            rightValue = self._rightOperand.evaluate(metaObj, resolver)
        else:
            #Try to retrieve value from the context (if it is not a literal)

            if Collection.isCollection(self._rightOperand):
                rightValue = self._rightOperand
            else:
                #Try to first resolve (allow string based comparison)
                try:
                    rightValue = resolver.resolve(self._rightOperand, metaObj)
                except:
                    rightValue =  self._rightOperand

            #Try to first resolve (allow string based comparison)
            try:
                leftValue = resolver.resolve(self._leftOperand, metaObj)
            except:
                leftValue =  self._leftOperand


        #Adjusting types for numeric comparison
        if (type(rightValue) != type(leftValue)) and (self._operator not in _rangeOperators ) and (self._operator not in _collectionOperators):
            if type(leftValue) in [int, float, complex, long] and type(rightValue)==str:
                rightValue = type(leftValue)(rightValue)
            elif type(rightValue) in [int, float, complex, long] and type(leftValue)==str:
                leftValue = type(rightValue)(leftValue)

        #for simple conditions and str Values allow only == or !=
        if (type(leftValue) == str and type(rightValue) == str) and (self._operator not in ["=", "!="]):
            raise Exception("Unknown operation over string or unresolvable keyword")

        #Perform comparison and return value
        if self._operator == "=":
            return self._negate ^ (leftValue == rightValue)
        elif self._operator == "!=":
            return self._negate ^ (leftValue != rightValue)
        elif self._operator == ">":
            return self._negate ^ (leftValue > rightValue)
        elif self._operator == "<":
            return self._negate ^ (leftValue < rightValue)
        elif self._operator == ">=":
            return self._negate ^ (leftValue >= rightValue)
        elif self._operator == "<=":
            return self._negate ^ (leftValue <= rightValue)
        elif self._operator == "in":
            return self._negate ^ (leftValue in rightValue)
        elif self._operator == "[]":
            return self._negate ^ (Range.isInRange(leftValue, self._rightOperand))
        elif self._operator == "{}":
            return self._negate ^ (Range.isInRangeStrict(leftValue, self._rightOperand))
        elif self._operator == "&&":
            return self._negate ^ (leftValue and rightValue)
        elif self._operator == "||":
            return self._negate ^ (leftValue or rightValue)
        else:
            raise Exception("Unknown operator??")
